<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style type="text/css">
    * {
      margin: 0;
      padding: 0;
    }
    .wrap {
      width: 800px;
      margin: 20px auto;
      font-size: 13px;
      font-family: 'Lucida Grande', Verdana;
    }
    #file-btn {
      color: #fff;
      background-color: #428bca;
      border-color: #357ebd;
      display: inline-block;
      padding: 10px;
      border-radius: 4px;
      position: relative;
      cursor: pointer;
      -webkit-box-shadow: inset 0 -4px 0 #2a6496;
      box-shadow: inset 0 -4px 0 #2a6496;
      border: 0;
      text-decoration: none
    }
    #file-btn input {
      opacity: 0;
      width: 62px;
      height: 26px;
      position: absolute;
      cursor: pointer;
      top: 0;
      left: 0;
    }
    .file-list {
      display: table;
      width: 100%;
      margin-top: 20px;
      border: 1px solid #ccc
    }
    .file-list a {
      text-decoration: none;
      color: #0095C1
    }
    .upload-list {
      display: table-row;
    }
    .upload-list span {
      border-top: 1px solid #eee;
    }
    .file-list .th {
      display: table-row;
      background: #f5f5f5;
    }
    .file-list .th span {
      color: #999
    }
    .upload-name {
      width: 25%;
    }
    .upload-progress {
      width: 25%;
    }
    .upload-progress strong {
      display: block;
      height: 6px;
      line-height: 0;
      background: #DBE1E9
    }
    .upload-size {
      min-width: 140px;
    }
    .upload-size i {
      font-style: normal;
    }
    .upload-progress i {
      display: block;
      width: 0;
      height: 6px;
      line-height: 0;
      background: #3482da;
      -webkit-transition: .5s width ease;
      -moz-transition: .5s width ease;
      -o-transition: .5s width ease;
      -ms-transition: .5s width ease;
      transition: .5s width ease;
    }
    .file-list span {
      padding: 10px 5px;
      display: table-cell;
      border-right: 1px solid #eee;
      color: #333
    }
    .file-list span:last-child {
      border-right: 0 none;
    }
    #submit,
    #pause {
      color: #fff;
      background-color: #428bca;
      border-color: #357ebd;
      display: inline-block;
      padding: 8px 14px;
      border-radius: 3px;
      position: relative;
      cursor: pointer;
      -webkit-box-shadow: inset 0 -3px 0 #2a6496;
      box-shadow: inset 0 -3px 0 #2a6496;
      border: 0;
      margin-top: 10px;
      text-decoration: none
    }
    #addfile {
      margin: 10px 0;
      padding: 20px;
      width: 30%;
      color: #666;
      border: 1px dashed #777;
    }
    .into {
      box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.5) inset;
    }
    .not-support{
      padding: 6px 12px;
    }
  </style>
</head>

<body>
  <div class="wrap">
    <div id="addfile">请将文件拖入</div>
    <div class="file-list">
      <div class="upload-list th">
        <span>文件名</span>
        <span>上传进度</span>
        <span>文件大小</span>
        <span>文件类型</span>
        <span>状态</span>
        <span>操作</span>
      </div>
    </div>
    <a href="javascript:void(0);" id="submit">上传</a>
    <a href="javascript:void(0);" id="pause">暂停</a>
  </div>
</body>
<script type="text/javascript">
  (function (win) {
    var doc = win.document,
        body = doc.body,
        // 获取elements
        $ = function (str) {
          return doc.querySelectorAll(str);
        },
        // 文件size byte转换为KB或者MB
        format = function (s, k) {
          return s > 1024 * 1024 && !k ? (Math.round(s * 100 / (1024 * 1024)) / 100).toString() + 'MB' : (Math.round(s * 100 / 1024) / 100).toString() + 'KB';
        },
        // 给文件起名
        hs = function (str) {
          var hash = 1587866175, i, ch;
          for (i = str.length - 1; i >= 0; i--) {
            ch = str.charCodeAt(i);
            hash ^= ((hash << 5) + ch + (hash >> 2));
          }
          return (hash & 0x7FFFFFFF);
        },
        isDOM = function (dom) {
          return dom && (dom.nodeType === 1);
        },
        empty = function () { },
        Upload = function () {
          this._init.apply(this, arguments);
        };

    Upload.prototype = {
      constructor: Upload,
      _elementState: (function () {
        var _$ = function (id, cls) {
          var str = cls ? '>.upload-' + cls : '';
          str = cls == 'i' ? 'i' : str;
          return $('#upload' + id + str)[0];
        }

        return {
          state: function (id, msg) {
            var elem = _$(id, 'state');
            if (!elem) return this;
            elem.innerHTML = msg;
            return this;
          },
          progress: function (id, n) {
            var elem = _$(id, 'i');
            if (!elem) return this;
            var n = Math.min(n, 100);
            elem.style.width = n + '%';
            return this;
          },
          size: function (id, size) {
            var elem = _$(id, 'size i');
            if (!elem) return this;
            elem.textContent = format(size) + '/';
            return this;
          }
        }
      })(),
      _getFiles: function (files) {
        var i = 0, len = files.length, f, html = '', names = [], msg = '';
        if (!this.multifile && len > 1) {
          len = 1;
          alert('不支持多文件上传！');
        }
        for (; i < len; i++) {
          f = files[i];
          var name = f.name, size = f.size, type = name.split('.').pop().toLowerCase(),
            id = hs(f.lastModifiedDate.getTime() + '' + size + type);

          if (!name.split('.')[1]) {
            msg += name + ' 文件夹不允许上传！\n';
            continue;
          } else if (this.types !== '*' && !this._checkItem(this.types, type)) {
            msg += name + ' 文件类型被限制！\n';
            continue;
          }
          if (parseInt(format(size, 1)) > this.fileMax) {
            msg += name + ' 超出大小限制！\n';
            continue;
          }
          if (this._fileObject[id]) {
            msg += name + ' 已存在！\n';
            continue;
          }


          html += this.tpl.replace('$id$', id)
            .replace('$name$', name)
            .replace('$progress$', '<strong><i></i></strong>')
            .replace('$type$', type)
            .replace('$size$', '<i></i>' + (size > 1024 * 1024 ? format(size) : format(size, true)))
            .replace('$state$', '检测中..')
            .replace('$remove$', '<a href="javascript:;" data-id="' + id + '" class="upload-remove">删除</a>');

          names.push(id + '.' + type);
          //存储二进制数据对象
          this._fileObject[id] = {
            file: f,
            start: 0,
            pause: true
          };
        }
        msg !== '' && alert(msg);
        this.onFileChange(files);
        if (html !== '') {
          this.listParent.innerHTML += html;
          //发送Ajax获取文件size
          var xhr_check = new XMLHttpRequest(), _this = this,
            success = function (results) {
              for (var key in results) {
                var id = key.split('.')[0], o = _this._elementState,
                  f = _this._fileObject[id];
                if (!results[key]) {//没有上传过
                  o.state(id, '等待上传').size(id, 0);
                } else {
                  var n = parseInt(results[key] / f.file.size * 100);
                  o.progress(id, n);
                  if (n == 100) {//已传完
                    o.state(
                      id,
                      '<a target="_blank" href="uploads/' + key + '">已上传过</a>'
                    ).size(id, f.file.size);
                    _this._fileObject[id] = null;
                    delete _this._fileObject[id];
                  } else {//未传完
                    o.state(id, '等待续传').size(id, results[key]);
                    f.start = results[key];
                  }
                }
              }
            };

          xhr_check.open('get', this.checkFile + '?fileNames=' + names.join('|'), true);
          xhr_check.onreadystatechange = function (e) {
            if (this.readyState == 4) {
              if (this.status == 200) {
                try {
                  var json = JSON.parse(this.responseText);
                } catch (e) {
                  _this.error('Ajax获取文件状态失败！');
                  return;
                }
                json.status && json.data && success(json.data);
              }
            }
          }
          xhr_check.send();
        }
      },
      fileUpload: function (name, type, file, start) {
        if (this._fileObject[name].pause) return;
        var xhr = new XMLHttpRequest(), start = start || this._fileObject[name].start, _this = this,
          fd = new FormData();
        //切分文件块，准备传输
        fd.append(this.fileName, file.slice(start, start + this.fileSplitSize));
        this.formData.fileName = name + '.' + type;
        for (var key in this.formData) {
          this.formData.hasOwnProperty(key) && fd.append(key, this.formData[key]);
        }

        xhr.open(this.method, this.uploader, true);
        xhr.setRequestHeader("X_Requested_With", 'XMLHttpRequest');
        xhr.upload.addEventListener("progress", function (e) {
          var n = parseInt((e.loaded + start) / file.size * 100);
          _this._elementState.progress(name, n).size(name, e.loaded + start);
        }, false);

        xhr.onreadystatechange = function (e) {
          if (this.readyState == 4) {
            if (this.status == 200) {
              try {
                var data = JSON.parse(this.responseText);
              } catch (e) {
                _this.error('上传失败！');
                return;
              }
              
              if (data.data && data.data.result) {
                //判断文件是否传输完毕
                if (start + _this.fileSplitSize >= file.size) {
                  //上传完毕 清除引用
                  _this._fileObject[name] = null;
                  delete _this._fileObject[name];
                  _this._elementState.state(name, '上传成功').size(name, file.size);
                  _this.onUploadSucc(name);
                  console.log(name + '上传成功！');
                } else {
                  //未传完 继续
                  start += _this.fileSplitSize;
                  _this._fileObject[name].start = start;
                  _this.fileUpload(name, type, file, start);

                }
              }
            } else {
              _this.onUploadErr();
              _this._elementState.state(name, '上传失败');
              _this.error('上传失败！');
            }
          }
        }
        xhr.send(fd);
      },
      _checkItem: function (strs, str) {
        var reg = new RegExp('(^|\\|)' + str + '(\\||$)', 'i');
        return reg.test(strs);
      },
      _init: function (opt) {
        opt = opt || {};
        // [必填项] String 拖拽文件后检查文件是否上传过
        this.checkFile = opt.checkFile;
        // [必填项] String 提交到后端处理的文件地址
        this.uploader = opt.uploader;
        // [必填项] Object 选择文件的button
        this.selectBtn = opt.selectBtn;
        // [必填项] Object 列表元素父级element
        this.listParent = opt.listParent;
        // [必填项] 拖拽上传dom元素
        this.dragElement = opt.dragElement;

        // String 文件上传类型限制，用|分割 
        this.types = opt.types || '*';
        // String 后端接受到文件键值
        this.fileName = opt.fileName || 'file';
        // boole 是否开启多文件上传 默认开启
        this.multifile = opt.multifile || true;
        // 上传列表项目
        this.listItem = opt.listItem || 'name|state';
        // String 请求方式
        this.method = opt.method || 'post';
        // Object 提交到后端的附加参数格式为json
        this.formData = opt.formData || {};
        // Number 单个文件的大小限制 单位kb 默认2MB
        this.fileMax = opt.fileMax || 2048;
        // Number 断点分割的文件块大小 单位bytes 默认1MB
        this.fileSplitSize = opt.fileSplitSize || 1024 * 1024;
        
        // Function 获取file列表
        this.onFileChange = opt.onFileChange || empty;
        // Function 每个文件上传成功回调
        this.onUploadSucc = opt.onUploadSucc || empty;
        // Function 每个文件上传失败回调
        this.onUploadErr = opt.onUploadErr || empty;
        // 当前的存储file对象数组
        this._fileObject = {};
        
        if (!isDOM(this.listParent)) {
          this.error('参数listParent不是一个DOM节点！');
          return;
        }
        if (typeof window.requestAnimationFrame !== "function") {
          this.listParent.innerHTML += '<p class="not-support">您的浏览器不支持html5断点续传，请使用IE10+,Firefox,Chrome</p>';
          return;
        }

        if (this.selectBtn && this.selectBtn.nodeType === 1) {
          this.fileInput = doc.createElement('input');
          this.fileInput.type = 'file';
          this.fileInput.name = this.fileName + '[]';
          this.fileInput.multiple = this.multifile;
          this.fileInput.style.display = 'none';
          body.appendChild(this.fileInput);
        }
        //初始化执行函数
        this._setTpl();
        this._addEvent();
      },
      _setTpl: function () {
        var item = ['<div class="upload-list" id="upload$id$">'];
        var arr = this.listItem.split('|');
        for (var i = 0; arr[i]; i++) {
          if (arr[i] == 'remove') {
            item.push('<span>$' + arr[i] + '$</span>');
            continue;
          }
          item.push('<span class="upload-' + arr[i] + '">$' + arr[i] + '$</span>');
        }
        this.tpl = item.join('') + '</div>';
      },
      pause: function (id) {
        if (id * 1 === id) {//单个暂停
          this._fileObject[id] && (this._fileObject[id].pause = true);
        } else {//全部暂停
          for (var key in this._fileObject) {
            this._fileObject[key].pause = true;
          }
        }
      },
      upload: function (id) {
        if (id * 1 == id) {//单个开始上传
          if (this._fileObject[id]) {
            var o = this._fileObject[id];
            o.pause = false;
            this.fileUpload(key, o.file.name.split('.').pop().toLowerCase(), o.file);
          }
        } else {//全部开始上传
          var o;
          for (var key in this._fileObject) {
            o = this._fileObject[key];
            if (o.pause == false && o.start > 0) {
              continue;
            }
            //关闭所有暂停
            o.pause = false;
            this.fileUpload(key, o.file.name.split('.').pop().toLowerCase(), o.file);
          }
        }

      },
      remove: function (id) {
        var item;
        if (id * 1 == id) {
          item = $('#upload' + id)[0];
          if (item) {
            this._fileObject[key] && (this._fileObject[id] = true);
            this._fileObject[id] = null;
            delete this._fileObject[id];
            item.parentNode.removeChild(item);
          }
        } else {
          for (var key in this._fileObject) {
            item = $('#upload' + key)[0];
            if (item) {
              this._fileObject[key] && (this._fileObject[key] = true);
              this._fileObject[key] = null;
              delete this._fileObject[key];
              item.parentNode.removeChild(item);
            }
          }
        }
      },
      _addEvent: function () {
        var _this = this;

        _this.listParent.addEventListener('click', function (e) {
          var target = e.target;
          if (target && target.className == 'upload-remove') {
            //删除单个文件
            var id = target.getAttribute('data-id');
            id * 1 == id && _this.remove(id);
          }

        }, false);
        //拖放选择文件
        if (isDOM(_this.dragElement)) {
          _this.dragElement.addEventListener('dragover', function (e) {
            e.preventDefault();
            this.className = 'into';
          }, false);
          _this.dragElement.addEventListener('drop', function (e) {
            e.preventDefault();
            this.className = '';
            _this._getFiles(e.dataTransfer.files);
          }, false);
        }

        //文件选择控件
        if (isDOM(_this.selectBtn)) {
          _this.fileInput.addEventListener('change', function (e) {
            _this._getFiles(e.target.files);
          }, false);
          _this.selectBtn.addEventListener('click', function (e) {
            _this.fileInput.click();
          }, false);
        }

      },
      error: function (str) {
        throw new Error(str);
      }
    };
    win.upload = function (opt) {
      return new Upload(opt);
    }
  })(window);
</script>
<script type="text/javascript">
  var $ = function (str) {
    return document.querySelector(str);
  }
  var up = upload({
    listParent: $('.file-list'),
    dragElement: $('#addfile'),
    listItem: 'name|progress|size|type|state|remove',
    fileName: 'file',
    fileSplitSize: 1024 * 1024 * 2,
    multifile: true,
    fileMax: 999999999,
    checkFile: 'http://localhost/shop/public/index.php/v1/checkFile',
    uploader: 'http://localhost/shop/public/index.php/v1/upload',
    types: '*'
  });
  $('#submit').onclick = function () {
    up.upload();
  }
  $('#pause').onclick = function () {
    up.pause();
  }
</script>

</html>